package com.tarena.charging.order.service;

import cn.tedu.charging.common.pojo.JsonResult;
import cn.tedu.charging.common.pojo.param.GunStatusUpdateParam;
import cn.tedu.charging.common.pojo.vo.StationInfoVO;
import cn.tedu.charging.common.pojo.vo.UserInfoVO;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tarena.charging.order.constant.TopicConstant;
import com.tarena.charging.order.feign.CostClient;
import com.tarena.charging.order.feign.DeviceClient;
import com.tarena.charging.order.feign.UserClient;
import com.tarena.charging.order.mapper.BillExceptionMapper;
import com.tarena.charging.order.mapper.BillSuccessMapper;
import com.tarena.charging.order.mqtt.MqttProducer;
import com.tarena.charging.order.pojo.dto.ChargingMessageDto;
import com.tarena.charging.order.pojo.dto.OrderDelayMessageDto;
import com.tarena.charging.order.pojo.dto.StationInfoDto;
import com.tarena.charging.order.pojo.dto.UserCarInfoDto;
import com.tarena.charging.order.pojo.param.OrderAddParam;
import com.tarena.charging.order.pojo.po.BillExceptionPO;
import com.tarena.charging.order.pojo.po.BillSuccessPO;
import com.tarena.charging.order.quartz.DeviceCheckJob;
import com.tarena.charging.order.rabbit.OrderDelayProducer;
import com.tarena.charging.order.websocket.WebSocketServer;
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class OrderService {
    @Autowired
    private DeviceClient deviceClient;
    @Autowired
    private UserClient userClient;
    @Autowired
    private CostClient costClient;
    @Autowired
    private MqttProducer mqttProducer;
    @Autowired
    private OrderDelayProducer producer;
    @Autowired private BillSuccessMapper billSuccessMapper;
    @Autowired
    private BillExceptionMapper billExceptionMapper;
    @Autowired
    private WebSocketServer webSocketServer;

    // 创建订单的流程 当前业务并不会直接创建bill 需要明确success还是fail
    public String orderCreate(OrderAddParam param) {
        //1. 准备一个全局唯一的字符串订单编号
        String billId=generateBillId();
        //2.参数传递的数据包括用户 包括设备 足够创建订单 但是 用户是否合法 车辆是否存在 充电站是否存在 枪的状态是否可用
        //作为订单的接口 要检查一遍,不能直接相信前端的传参
        //调用device服务 获取device信息判断结果 设备是否存在 user服务 服务调用服务 返回参数 封装dto
        StationInfoDto stationInfoDto=getStationInfoByGunId(param.getGunId());
        if (stationInfoDto==null||stationInfoDto.getStationId()==null){
            throw new RuntimeException("设备站场不存在 订单创建失败");
        }
        //调用user服务 获取user信息判断结果 获取车辆信息 device服务
        UserCarInfoDto userCarInfoDto=getUserCarInfoByUserId(param.getUserId());
        if (userCarInfoDto==null||userCarInfoDto.getCarId()==null){
            throw new RuntimeException("车辆信息不存在 订单创建失败");
        }
        //订单马上要生成了 开始充电了 枪已经插上了 更新枪的状态 1空闲 2使用中 3故障 4离线 device服务
        Boolean success=updateGunBusy(param.getGunId());
        if (!success){
            throw new RuntimeException("更新枪状态失败 订单创建失败");
        }
        //创建设备自检的定时任务 1分钟后判断自检结果
        try {
            DeviceCheckJob job=new DeviceCheckJob(billId,param.getGunId(),param.getUserId());
        } catch (SchedulerException e) {
            log.error("定时自检任务创建失败");
        }
        //创建订单命令 发送生产消息 到emqx的主题队列 同时 设备(mqtt.fx模拟) 接收到当前命令开始充电
        startCharging(billId,param.getGunId(),param.getPileId());
        //充电 有一个最长充电时间 不同设备 充电时长不同,一旦超过这个时间 就认为订单异常立刻停止充电.
        //组织一个订单延迟消息
        OrderDelayMessageDto orderDelayMessageDto=new OrderDelayMessageDto();
        orderDelayMessageDto.setBillId(billId);
        orderDelayMessageDto.setUserId(param.getUserId());
        producer.sendDelayMessage(orderDelayMessageDto);
        //不同设备 设置延迟队列 死信队列
        return billId;
    }

    private void startCharging(String billId, Integer pileId, Integer gunId) {
        //准备主题 不同的充电桩 都会订阅主题 防止订阅消息 错乱 每个电装订阅唯一主题
        String topic= TopicConstant.START_CHARGING_PREFIX+pileId;
        ChargingMessageDto message=new ChargingMessageDto();
        message.setBillId(billId);
        message.setGunId(gunId);
        message.setPileId(pileId);
        message.setMsg("start checking");
        //转化成字符串 序列化和反序列化
        String json = JSON.toJSONString(message);
        log.info("订单即将发送消息到主题:{},消息内容:{}",topic,json);
        mqttProducer.send(topic,json);
    }

    private Boolean updateGunBusy(Integer gunId) {
        log.info("更新枪状态为使用 gunId:{}",gunId);
        GunStatusUpdateParam param=new GunStatusUpdateParam();
        param.setGunId(gunId);
        //这种状态机种的状态属性 应该使用常量或者枚举 否则容易造成代码维护混淆
        param.setStatus(2);
        JsonResult<Boolean> result = deviceClient.updateGunStatus(param);
        Boolean success=false;
        if (result!=null){
            Boolean data = result.getData();
            if (data!=null){
                success=data;
            }
        }
        log.info("更新枪状态为使用 gunId:{},success:{}",gunId,success);
        return success;
    }

    private UserCarInfoDto getUserCarInfoByUserId(Integer userId) {
        log.info("查询用户车辆信息 userId:{}",userId);
        JsonResult<UserInfoVO> result = userClient.getUserCarInfo(userId);
        UserCarInfoDto dto=null;
        if (result!=null){
            UserInfoVO userInfoVO = result.getData();
            if (userInfoVO!=null){
                dto=new UserCarInfoDto();
                dto.setCarId(userInfoVO.getCarId());
            }
        }
        log.info("查询用户车辆信息 userId:{},dto:{}",userId,dto);
        return dto;
    }

    private StationInfoDto getStationInfoByGunId(Integer gunId) {
        log.info("查询站场 调用 设备 gunId:{}",gunId);
        //远程调用 deviceClient 解析jsonResult
        JsonResult<StationInfoVO> result = deviceClient.getStationInfo(gunId);
        StationInfoDto dto=null;
        if (result!=null){
            //连接调用成功了
            StationInfoVO stationInfoVO= result.getData();
            if (stationInfoVO!=null){
                dto=new StationInfoDto();
                dto.setStationId(stationInfoVO.getId());
            }
        }
        log.info("查询站场 调用 设备 gunId:{},dto:{}",gunId,dto);
        return dto;
    }

    private String generateBillId() {
        //编号的数据 采用uuid唯一值
        return UUID.randomUUID().toString().replaceAll("-","");
    }

    public void checkOrderStatus(OrderDelayMessageDto dto) {
        //1.取得2个参数
        String billId=dto.getBillId();
        Integer userId=dto.getUserId();
        //2.检查成功订单状态
        //select * from charging_bill_success where bill_id=#{}
        QueryWrapper<BillSuccessPO> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("bill_id",billId);
        BillSuccessPO billSuccessPO = billSuccessMapper.selectOne(queryWrapper);
        if (billSuccessPO!=null){
            log.info("延迟任务查询到订单:{}",billSuccessPO);
            if (billSuccessPO.getBillStatus().equals(2)){
                log.info("延迟任务结束,当前订单状态正常");
                return;
            }
        }
        //代码 执行到这里 说明订单有问题
        //修改订单状态为3 记录订单异常数据 到bill_exception
        billSuccessMapper.updateBillStatus(billId,3);
        BillExceptionPO poParam=new BillExceptionPO();
        poParam.setBillId(billId);
        poParam.setDeleted(false);
        poParam.setElectricity(new BigDecimal(1.5));
        poParam.setCreateTime(new Date());
        poParam.setTemperature(0.25f);
        poParam.setVoltage(new BigDecimal(10));
        poParam.setBillStarttime(new Date());
        billExceptionMapper.insert(poParam);
        //webSocket通知用户 充电没冲完 赶紧走 枪坏了
        webSocketServer.pushMessage(userId,"当前充电桩已坏 请收好优惠券,离开");
    }
}
